﻿//-------------------------------------------------------------------
//	Copyright (c) 2010-2014  Zhirnov Andrey
//	This file is part of the "UX Standard Lib" project.
//	See copyright notice in "UX_STL_Ext.h".
//-------------------------------------------------------------------

#include "GLSystem.h"

namespace UX_STL
{
namespace GLShell
{	

/*
=================================================
	конструктор
=================================================
*/
	GLSystem::GLSystem():
		/*Params*/	_uGLSLVersion(0),	_uGLVersion(0),
		/*other*/	_totalVRAM(0),		_eGPUModel(e_gpu_model::UNKNOWN)
	{
	}

/*
=================================================
	деструктор
=================================================
*/
	GLSystem::~GLSystem()
	{
	}
	
/*
=================================================
	инициализация
=================================================
*/
	bool GLSystem::_Init()
	{
		_uGLVersion = C_GL_MAJOR_VERSION*100 + C_GL_MINOR_VERSION*10;

		// switch GLSL version //
		if ( _uGLVersion >= 330 )
			_uGLSLVersion = _uGLVersion;
		else
			switch ( _uGLVersion )
			{
				case 320 :	_uGLSLVersion = 150;	break;
				case 310 :	_uGLSLVersion = 140;	break;
				case 300 :	_uGLSLVersion = 130;	break;
				case 210 :	_uGLSLVersion = 120;	break;
				case 200 :	_uGLSLVersion = 110;	break;
				default  :	_uGLSLVersion = 0;
			}
		
		if ( C_GL_ARB_debug_output )
			InitDebugOutput();

		string	s_gpu_vendor = (char*)glGetString(GL_VENDOR);
		
		// GPU Model
		usize	u_pos = 0;
		if ( s_gpu_vendor.Find( "AMD", u_pos ) )	_eGPUModel = e_gpu_model::AMD;		else
		if ( s_gpu_vendor.Find( "ATI", u_pos ) )	_eGPUModel = e_gpu_model::ATI;		else
		if ( s_gpu_vendor.Find( "NVIDIA", u_pos ) )	_eGPUModel = e_gpu_model::NVIDIA;	else
		if ( s_gpu_vendor.Find( "INTEL", u_pos ) )	_eGPUModel = e_gpu_model::INTEL;	else
													_eGPUModel = e_gpu_model::UNKNOWN;
		

		// NVidia //
		if ( _eGPUModel == e_gpu_model::NVIDIA )
		{
			#define GL_GPU_MEM_INFO_TOTAL_AVAILABLE_MEM_NVX		0x9048
			
			GLint	a_free_mem[4] = {0};
			GL_CALL( glGetIntegerv( GL_GPU_MEM_INFO_TOTAL_AVAILABLE_MEM_NVX, a_free_mem ) );
			_totalVRAM = Bytes( a_free_mem[0] );

			#undef GL_GPU_MEM_INFO_TOTAL_AVAILABLE_MEM_NVX
		}
		else
		// ATI / AMD //
		if ( _eGPUModel == e_gpu_model::AMD )
		{/*
			winapi::PFNWGLGETGPUIDSAMDPROC	wglGetGPUIDsAMD  = (winapi::PFNWGLGETGPUIDSAMDPROC)winapi::wglGetProcAddress("wglGetGPUIDsAMD");
			winapi::PFNWGLGETGPUINFOAMDPROC	wglGetGPUInfoAMD = (winapi::PFNWGLGETGPUINFOAMDPROC)winapi::wglGetProcAddress("wglGetGPUInfoAMD");

			if ( wglGetGPUIDsAMD != null and wglGetGPUInfoAMD != null )
			{
				uint	n	= wglGetGPUIDsAMD(0, 0);
				uint *	ids	= new uint[n];
				size_t	total_mem_mb = 0;
				wglGetGPUIDsAMD( n, ids );
				wglGetGPUInfoAMD( ids[0], WGL_GPU_RAM_AMD, GL_UNSIGNED_INT, sizeof(size_t), &total_mem_mb );
				delete [] ids;

				_uTotalVRAM = (uint)total_mem_mb;
			}*/
		}


		// set default values //
		glHint( GL_FRAGMENT_SHADER_DERIVATIVE_HINT, GL_NICEST );
		glHint( GL_TEXTURE_COMPRESSION_HINT, GL_NICEST );
		glHint( GL_POLYGON_SMOOTH_HINT, GL_NICEST );
		glHint( GL_LINE_SMOOTH_HINT, GL_NICEST );
		

		// init systems //
		CHECK_ERR( _cStMan._Init() );
		CHECK_ERR( _cBatchRender.Create() );
		return true;
	}
	
/*
=================================================
	
=================================================
*/
	void GLSystem::_Destroy()
	{
		_cBatchRender._Destroy();
	}

/*
=================================================
	записывает в строку информацию о OpenGL
=================================================
*/
	void GLSystem::GetOpenGLInfo(string &sInfo) const
	{
		sInfo << "---------------\nOpenGL info\n---------------"
			  << "\nRenderer:  " << (const char*)glGetString(GL_RENDERER)
			  << "\nVersion:   " << (const char*)glGetString(GL_VERSION)
			  << "\nVendor:    " << (const char*)glGetString(GL_VENDOR)
			  << "\nGLSL:      " << (const char*)glGetString(GL_SHADING_LANGUAGE_VERSION)
			  << "\n";
	}
	
/*
=================================================
	записывает в строку параметры OpenGL
=================================================
*/
	void GLSystem::GetOpenGLParams(string &sParams) const
	{
		sParams << "---------------\nOpenGL params\n---------------"
				<< "\nMax anisotropy:        " << C_GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT
				<< "\nMax attribs:           " << C_GL_MAX_VERTEX_ATTRIBS
				<< "\nMax image units:       " << C_GL_MAX_IMAGE_UNITS
				<< "\nMax texture units:     " << C_GL_MAX_TEXTURE_IMAGE_UNITS
				<< "\nMax texture size:      " << C_GL_MAX_TEXTURE_SIZE << " px"
				<< "\nMax texture 3d size:   " << C_GL_MAX_3D_TEXTURE_SIZE << " px"
				<< "\nMax texture layers:    " << C_GL_MAX_ARRAY_TEXTURE_LAYERS
				<< "\nMax cube map size:     " << C_GL_MAX_CUBE_MAP_TEXTURE_SIZE << " px"
				<< "\nMax rect tex size:     " << C_GL_MAX_RECTANGLE_TEXTURE_SIZE << " px"
				<< "\nMax render buf size:   " << C_GL_MAX_RENDERBUFFER_SIZE << " px"
				<< "\nMax texture buf size:  " << Bytes( C_GL_MAX_TEXTURE_BUFFER_SIZE ).ToString()
				<< "\nMax viewport dim:      " << Max( C_GL_MAX_VIEWPORT_DIMS[0], C_GL_MAX_VIEWPORT_DIMS[1] ) << " px"
				<< "\nMax color tex samples: " << C_GL_MAX_COLOR_TEXTURE_SAMPLES
				<< "\nMax depth tex samples: " << C_GL_MAX_DEPTH_TEXTURE_SAMPLES
				<< "\nMax integer samples:   " << C_GL_MAX_INTEGER_SAMPLES
				<< "\nMax color attachments: " << C_GL_MAX_COLOR_ATTACHMENTS
				<< "\nMax draw buffers:      " << C_GL_MAX_DRAW_BUFFERS
				<< "\nMax viewports:         " << C_GL_MAX_VIEWPORTS
				//<< "\nMax element indices:   " << string().FormatI( C_GL_MAX_ELEMENTS_INDICES, 3, ' ', 10 )
				//<< "\nMax element vertices:  " << string().FormatI( C_GL_MAX_ELEMENTS_VERTICES, 3, ' ', 10 )
				<< "\nMax UBO bindings:      " << C_GL_MAX_UNIFORM_BUFFER_BINDINGS
				<< "\nMax UBO size:          " << Bytes( C_GL_MAX_UNIFORM_BLOCK_SIZE ).ToString()
				<< "\nMax tess gen level:    " << C_GL_MAX_TESS_GEN_LEVEL
				<< '\n';
	}

/*
=================================================
	возвращает размер свободной видео памяти
=================================================
*/
	void GLSystem::GetVideoMemory(Bytes &total, Bytes &free) const
	{
		GLint	a_free_mem[4] = {0};

		if ( _eGPUModel == e_gpu_model::NVIDIA )
		{
			#define GL_GPU_MEM_INFO_CURRENT_AVAILABLE_MEM_NVX	0x9049

			GL_CALL( glGetIntegerv( GL_GPU_MEM_INFO_CURRENT_AVAILABLE_MEM_NVX, a_free_mem ) );
		
			#undef GL_GPU_MEM_INFO_CURRENT_AVAILABLE_MEM_NVX
		}
		else
		if ( _eGPUModel == e_gpu_model::AMD )
		{
			#define GL_TEXTURE_FREE_MEMORY_ATI        0x87FC

			GL_CALL( glGetIntegerv( GL_TEXTURE_FREE_MEMORY_ATI, a_free_mem ) );
			
			#undef GL_TEXTURE_FREE_MEMORY_ATI
		}

		total = _totalVRAM;
		free  = Bytes( a_free_mem[0] );
	}

/*
=================================================
	возвращает точку в оконных координатах
=================================================
*/
	void GLSystem::GetScreenPos(const fvec3 &vPoint, uvec2 &vOnScr) const
	{
		vOnScr = Round<uint>( fmat4::Project( vPoint, MSTACK()->GetMatrixMVP().Transpose(),
								SMAN()->GetViewport().To<frect>() ).xy() );
	}

/*
=================================================
	возвращает точку из Z-буфера
=================================================
*/
	void GLSystem::GetWorldPos(const uvec2 &vOnScr, fvec3 &vPoint) const
	{
		fvec3	v_win = fvec3( vOnScr.To<fvec2>(), 0.0f );

        GL_CALL( glReadPixels( vOnScr.x, vOnScr.y, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT, &v_win.z ) );

		vPoint = fmat4::UnProject( v_win, MSTACK()->GetMatrixMVP().Transpose(), SMAN()->GetViewport().To<frect>() );
	}
	
/*
=================================================
	возвращает глубину
=================================================
*/
	void GLSystem::GetScreenDepth(const uvec2 &vOnScr, float &fDepth) const
	{
        GL_CALL( glReadPixels( vOnScr.x, vOnScr.y, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT, &fDepth ) );
	}

/*
=================================================
	возвращает точку на ближней плоскости отсечения
=================================================
*/
	void GLSystem::GetNearPlanePos(const uvec2 &vOnScr, fvec3 &vPoint) const
	{
		const fvec3	v_win = fvec3( vOnScr.To<fvec2>(), 0.0f );

		vPoint = fmat4::UnProject( v_win, MSTACK()->GetMatrixMVP().Transpose(), SMAN()->GetViewport().To<frect>() );
	}

/*
=================================================
	возвращает точку на дальней плоскости отсечения
=================================================
*/
	void GLSystem::GetFarPlanePos(const uvec2 &vOnScr, fvec3 &vPoint) const
	{
		const fvec3	v_win = fvec3( vOnScr.To<fvec2>(), 1.0f );

		vPoint = fmat4::UnProject( v_win, MSTACK()->GetMatrixMVP().Transpose(), SMAN()->GetViewport().To<frect>() );
	}

/*
=================================================
	инициализирует вывод дебаг сообщений
=================================================
*/
	bool GLSystem::IsExtSupported(const char *pExtName) const
	{
		if ( pExtName == null )
			return false;

		int			i_num_ext	= 0;
		const char *p_ext		= null;

		GL_CALL( glGetIntegerv( GL_NUM_EXTENSIONS, &i_num_ext ) );
	
		for (int i = 0; i < i_num_ext; ++i)
		{
			GL_CALL( p_ext = (const char *)glGetStringi( GL_EXTENSIONS, i ) );

			if ( p_ext == null )
				break;

			if ( ::_hidden_::strcmp( p_ext, pExtName ) == 0 )
				return true;
		}

		return false;
	}
	
/*
=================================================
	инициализирует вывод дебаг сообщений
=================================================
*/
	bool GLSystem::InitDebugOutput(bool bSync)
	{
		CHECK_ERR( C_GL_ARB_debug_output );

		if ( bSync ) {	GL_CALL( glEnable(  GL_DEBUG_OUTPUT_SYNCHRONOUS_ARB ) ); }
		else		 {	GL_CALL( glDisable( GL_DEBUG_OUTPUT_SYNCHRONOUS_ARB ) ); }

		// enable all messages
		GL_CALL_R( glDebugMessageControl( GL_DONT_CARE, GL_DONT_CARE, GL_DONT_CARE, 0, null, GL_TRUE ) );
		GL_CALL_R( glDebugMessageCallback( &_s_DebugOutput, this ) );
		return true;
	}
	
/*
=================================================
	устанавливает функцию вызываемую при
	возникновении ошибки в OpenGL
=================================================
*/
	bool GLSystem::SetDebugCallBack(PGLDebugProc_t pProc, void *pParametr)
	{
		CHECK_ERR( C_GL_ARB_debug_output );

		if ( pProc == null )
		{
			pProc	  = &_s_DebugOutput;
			pParametr = (void *)this;
		}
		GL_CALL_R( glDebugMessageCallback( pProc, pParametr ) );
		return true;
	}
	
/*
=================================================
	функция обработки сообщений об ошибках
	записывает сообщения в лог
=================================================
*/
	void CALLBACK GLSystem::_s_DebugOutput(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar* message, GLvoid* userParam)
	{
		string	str( "Source: ");

		switch( source )
		{
			case GL_DEBUG_SOURCE_API_ARB				:	str << "OpenGL";				break;
			case GL_DEBUG_SOURCE_WINDOW_SYSTEM_ARB		:	str << "Window";				break;
			case GL_DEBUG_SOURCE_SHADER_COMPILER_ARB	:	str << "Shader Compiler";		break;
			case GL_DEBUG_SOURCE_THIRD_PARTY_ARB		:	str << "Third Party";			break;
			case GL_DEBUG_SOURCE_APPLICATION_ARB		:	str << "Application";			break;
			case GL_DEBUG_SOURCE_OTHER_ARB				:	str << "Other";					break;
			default										:	str << "Unknown";
		}

		str << ", Type: ";

		switch ( type )
		{
			case GL_DEBUG_TYPE_ERROR_ARB				:	str << "Error";					break;
			case GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR_ARB	:	str << "Deprecated behavior";	break;
			case GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR_ARB	:	str << "Undefined behavior";	break;
			case GL_DEBUG_TYPE_PORTABILITY_ARB			:	str << "Portability";			break;
			case GL_DEBUG_TYPE_PERFORMANCE_ARB			:	str << "Performance";			break;
			case GL_DEBUG_TYPE_OTHER_ARB				:	str << "Other";					break;
			default										:	str << "Unknown";
		}

		str << ", Id: 0x" << string().FormatI(id, 16) << ", Severity: ";

		switch ( severity )
		{
			case GL_DEBUG_SEVERITY_HIGH_ARB				:	str << "High";					break;
			case GL_DEBUG_SEVERITY_MEDIUM_ARB			:	str << "Medium";				break;
			case GL_DEBUG_SEVERITY_LOW_ARB				:	str << "Low";					break;
			default										:	str << "Unknown";
		}

		str << ", Message: \"" << message << "\".";

		LOG( str.cstr(), e_log_msg::ERROR_NL_2 );
	}

//-------------------------------------------------------------------

}	// GLShell
}	// UX_STL